/**
 * @file lv_port_disp_templ.c
 *
 */

/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include <stdbool.h>
#include <rtthread.h>

/*********************
 *      DEFINES
 *********************/
#define USE_STATIC_BUFF         0
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
static void disp_fb_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//        const lv_area_t * fill_area, lv_color_t color);

/**********************
 *  STATIC VARIABLES
 **********************/
rt_device_t lcd_dev = RT_NULL;
struct rt_device_graphic_info disp_info = {0};
static lv_disp_drv_t disp_drv;
static lv_disp_draw_buf_t draw_buf_dsc;
#if USE_STATIC_BUFF
#include "lcd_st7789.h"
static lv_color_t lv_disp_buff[LCD_W*LCD_H];
#endif
/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    lcd_dev = rt_device_find("lcd");
    RT_ASSERT(lcd_dev != RT_NULL);
    if (rt_device_open(lcd_dev, RT_DEVICE_OFLAG_RDWR) == RT_EOK)
    {
        rt_device_control(lcd_dev, RTGRAPHIC_CTRL_GET_INFO, &disp_info);
    }

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/
    lv_color_t *fbuf;
    int fbuf_num;
    #if 0
    fbuf_num = disp_info.width * 10;
    fbuf = rt_malloc(fbuf_num * sizeof(lv_color_t));
    if(!fbuf)
    {
        rt_kprintf("Error: alloc disp buf fail\r\n");
        return ;
    }
    #else
    if (disp_info.framebuffer == RT_NULL)
    {
        #if USE_STATIC_BUFF
        if((disp_info.width > LCD_W) || (disp_info.height > LCD_H))
        {
            rt_kprintf("width(%d) > LCD_W or height(%d) > LCD_H\r\n", disp_info.width, disp_info.height);
            return ;
        }
        fbuf_num = disp_info.width * disp_info.height;
        fbuf = lv_disp_buff;
        #else
        fbuf_num = disp_info.width * disp_info.height;
        fbuf = rt_malloc(fbuf_num * sizeof(lv_color_t));
        if(!fbuf)
        {
            rt_kprintf("Error: alloc disp buf fail\r\n");
            return ;
        }
        rt_kprintf("disp buff:%p\r\n", fbuf);
        #endif
    }
    else
    {
        fbuf = (void *)disp_info.framebuffer;
        fbuf_num = disp_info.width * disp_info.height;
    }
    #endif
    
    lv_disp_draw_buf_init(&draw_buf_dsc, fbuf, NULL, fbuf_num);

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/

    /*Set up the functions to access to your display*/

    /*Set the resolution of the display*/
    disp_drv.hor_res = disp_info.width;
    disp_drv.ver_res = disp_info.height;

    /*Used to copy the buffer's content to the display*/
    #if 1
    disp_drv.flush_cb = disp_flush;
    #else
    if (disp_info.framebuffer == RT_NULL)
    {
        disp_drv.flush_cb = disp_flush;
    }
    else
    {
        disp_drv.flush_cb = disp_fb_flush;
    }
    #endif

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc;

    /* Required for Example 3) */
    //disp_drv.full_refresh = 1;

    /* Fill a memory array with a color if you have GPU.
     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
     * But if you have a different GPU you can use with this callback.*/
    //disp_drv.gpu_fill_cb = gpu_fill;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

volatile bool disp_flush_enabled = true;

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void)
{
    disp_flush_enabled = true;
}

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void)
{
    disp_flush_enabled = false;
}

/*Flush the content of the internal buffer the specific area on the display
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    int x1, x2, y1, y2;
    
    x1 = area->x1;
    x2 = area->x2;
    y1 = area->y1;
    y2 = area->y2;
    
    if(x2 < 0)
        return ;
    if(y2 < 0)
        return ;
    if(x1 > (disp_info.width - 1))
        return ;
    if(y1 > (disp_info.height - 1))
        return ;
    
    int32_t act_x1 = x1 < 0 ? 0 : x1;
    int32_t act_y1 = y1 < 0 ? 0 : y1;
    int32_t act_x2 = (x2 > disp_info.width - 1) ? (disp_info.width - 1) : x2;
    int32_t act_y2 = (y2 > disp_info.height - 1) ? (disp_info.height - 1) : y2;
    
    uint32_t x;
    uint32_t y;
    
    for(y = act_y1; y <= act_y2; y++)
    {
        ((struct rt_device_graphic_ops *)(lcd_dev->user_data))->blit_line((const char *)color_p, act_x1, y, act_x2 - act_x1 + 1);
        color_p += (x2 - x1 + 1);
    }
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}
static void color_to16_maybe(lv_color16_t *dst, lv_color_t *src)
{
#if (LV_COLOR_DEPTH == 16)
    dst->full = src->full;
#else
    dst->ch.blue = src->ch.blue;
    dst->ch.green = src->ch.green;
    dst->ch.red = src->ch.red;
#endif
}
static void disp_fb_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
    int x1, x2, y1, y2;

    x1 = area->x1;
    x2 = area->x2;
    y1 = area->y1;
    y2 = area->y2;

    /*Return if the area is out the screen*/
    if (x2 < 0)
        return;
    if (y2 < 0)
        return;
    if (x1 > disp_info.width - 1)
        return;
    if (y1 > disp_info.height - 1)
        return;

    /*Truncate the area to the screen*/
    int32_t act_x1 = x1 < 0 ? 0 : x1;
    int32_t act_y1 = y1 < 0 ? 0 : y1;
    int32_t act_x2 = x2 > disp_info.width - 1 ? disp_info.width - 1 : x2;
    int32_t act_y2 = y2 > disp_info.height - 1 ? disp_info.height - 1 : y2;

    uint32_t x;
    uint32_t y;
    long int location = 0;

    /* 8 bit per pixel */
    if (disp_info.bits_per_pixel == 8)
    {
        uint8_t *fbp8 = (uint8_t *)disp_info.framebuffer;
        //TODO color convert maybe
        for (y = act_y1; y <= act_y2; y++)
        {
            for (x = act_x1; x <= act_x2; x++)
            {
                location = (x) + (y)*disp_info.width;
                fbp8[location] = color_p->full;
                color_p++;
            }
            color_p += x2 - act_x2;
        }
    }
    /* 16 bit per pixel */
    else if (disp_info.bits_per_pixel == 16)
    {
        lv_color16_t *fbp16 = (lv_color16_t *)disp_info.framebuffer;
        //TODO
        for (y = act_y1; y <= act_y2; y++)
        {
            for (x = act_x1; x <= act_x2; x++)
            {
                location = (x) + (y)*disp_info.width;
                color_to16_maybe(&fbp16[location], color_p);
                color_p++;
            }
            color_p += x2 - act_x2;
        }
    }
    /* 24 or 32 bit per pixel */
    else if (disp_info.bits_per_pixel == 24 || disp_info.bits_per_pixel == 32)
    {
        uint32_t *fbp32 = (uint32_t *)disp_info.framebuffer;
        //TODO
        for (y = act_y1; y <= act_y2; y++)
        {
            for (x = act_x1; x <= act_x2; x++)
            {
                location = (x) + (y)*disp_info.width;
                fbp32[location] = color_p->full;
                color_p++;
            }
            color_p += x2 - act_x2;
        }
    }

    struct rt_device_rect_info rect_info;

    rect_info.x = x1;
    rect_info.y = y1;
    rect_info.width = x2 - x1 + 1;
    rect_info.height = y2 - y1 + 1;
    rt_device_control(lcd_dev, RTGRAPHIC_CTRL_RECT_UPDATE, &rect_info);

    lv_disp_flush_ready(disp_drv);
}

/*OPTIONAL: GPU INTERFACE*/

/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*/
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//                    const lv_area_t * fill_area, lv_color_t color)
//{
//    /*It's an example code which should be done by your GPU*/
//    int32_t x, y;
//    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
//
//    for(y = fill_area->y1; y <= fill_area->y2; y++) {
//        for(x = fill_area->x1; x <= fill_area->x2; x++) {
//            dest_buf[x] = color;
//        }
//        dest_buf+=dest_width;    /*Go to the next line*/
//    }
//}


#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif
